package com.nowcoder.community.util;

import com.fasterxml.jackson.core.util.BufferRecycler;
import com.sun.xml.internal.fastinfoset.util.CharArrayArray;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.CharUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.javassist.compiler.ast.Keyword;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.Buffer;
import java.rmi.Remote;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;

/**
 * @BelongsProject: community
 * @BelongsPackage: com.nowcoder.community.util
 * @Author: Master_XXJS
 * @CreateTime: 2022-10-02  10:15
 * @Description: TODO
 * @Version: 1.0
 */
@Slf4j
@Component
public class SensitiveFilter {

    //敏感词的替换符
    private static final String REPLACEMENT = "***";
    //根节点,也就是第一个节点 ROOT
    private TrieNode rootNode = new TrieNode();

    //这个注解的意思是，进行初始化的意思，也就是说，当这个类进行加载的时候，优先执行的部分
    //构造方法  ——> @Autowired —— > @PostConstruct ——> 静态方法 （按此顺序加载）
    //在类初始化之前会先加载该使用该注解的方法；然后再执行类的初始化。
    @PostConstruct
    public void init(){
        //在这个敏感词的树进行构建之前，我们需要先获得敏感词

        try(
                //通过反射获得敏感词的输入流
                InputStream is = this.getClass().getClassLoader().getResourceAsStream("sensitive-words.txt");
                //通过缓冲流进行读取这个输入流
                BufferedReader reader = new BufferedReader(new InputStreamReader(is));
                ) {
            String keyword;
            //将自定义的敏感词，是一行一行的进行读取，不是一个一个字的读取，一行是 一个词，不是一个字
            while ((keyword = reader.readLine()) != null) {
                //将读取出来的敏感词添加到前缀树中
                this.addKeyword(keyword);
            }
        } catch (IOException e) {
            log.error("加载敏感词失败！" + e.getMessage());
        }

    }

    /**
     * 将敏感词添加到 前缀树 中
     * @param keyword 读取出来的敏感词
     */
    private void addKeyword(String keyword){
        TrieNode tempNode = rootNode;
        //一直进行循环的读取，一个树的节点不是存放的一个 词，而是一个字符
        for (int i = 0; i < keyword.length(); i++) {
            char c = keyword.charAt(i);
            //获取这个词的子节点
            TrieNode subNode = tempNode.getSubNode(c);

            if (subNode == null){
                //进行初始化子节点
                subNode = new TrieNode();
                //将读取出来的字符添加到下一个节点中
                tempNode.addSubNode(c,subNode);
            }

            //指向子节点，进入下一轮的循环
            tempNode = subNode;

            //设置结束的标记
            if(i == keyword.length() - 1){
                //如果，敏感词的判断已经进入到了 最后的阶段，那么就进行标记，这是一个敏感词
                tempNode.setKeywordEnd(true);
            }
        }
    }

    /**
     * 过滤敏感词
     * @param text 待过滤的文本
     * @return 过滤之后的文本
     */
    public String filter(String text){
        if (StringUtils.isBlank(text)){
            log.info("文本为空！");
            return null;
        }

        //指针 1,当前的树
        TrieNode tempNode = rootNode;
        //指针 2，开始的节点，上面的指针
        int begin = 0;
        //指针 3，当前位置的节点，下面的指针
        int position = 0;
        //结果
        StringBuilder sb = new StringBuilder();

        //使用上指针作为我们的循环条件，因为上指针走的慢
        while (begin < text.length()){
            char c = text.charAt(position);

            //跳过符号
            // true 是符号，false 是文字
            if (isSymbol(c)){
                //若指针1处于根节点，将此符号计入结果当中，并且让 指针2 向下走一步
                if (tempNode == rootNode){
                    //如果是根节点是字符的话，就直接将字符读取到过滤之后的文本当中，不需要进行过滤
                    sb.append(c);
                    begin++;
                }
                //无论符号是否是在开头或者是中间，指针 3 向下走一步
                position++;
                continue;
            }

            //检查下一级的节点，通过 map 进行key - c，看是否存在值，存在就是敏感词的范围
            tempNode = tempNode.getSubNode(c);
            if (tempNode == null){
                //以 begin 开头的字符串不是敏感词
                sb.append(text.charAt(begin));
                //进入到下一个位置
                position = ++begin;
                //重新指向根节点
                tempNode = rootNode;
            }else if (tempNode.isKeywordEnd()){
                //发现敏感词，将 begin —— position 的字符串进行替换掉
                sb.append(REPLACEMENT);
                //进入到下一个位置
                begin = ++position;
                //重新指向根节点
                tempNode = rootNode;
            }else{
                if (position < text.length() - 1){
                    //检查下一个字符
                    position++;
                }
            }
        }

        //返回过滤之后的结果
        return sb.toString();
    }

    /**
     * 判断当前的字符是否是 符号
     * @param c 当前判断的字符
     * @return
     */
    private boolean isSymbol(Character c) {
        // 0x2E80~0x9FFF 是东亚文字范围
        return !CharUtils.isAsciiAlphanumeric(c) && (c < 0x2E80 || c > 0x9FFF);
    }


    /**
     * 创建一个敏感词的 前缀树，根据自定义的敏感词的词库，建立一棵树结构
     */
    private class TrieNode{

        //关键词结束的标记
        private boolean isKeywordEnd = false;
        //子节点，key是下级的字符，value是下级的节点
        //这个map 是上一个节点所独有的，每一个节点都有自己的一个amp，这个map管理着自己的下节点（直接连接自己的节点）
        private Map<Character,TrieNode> subNodes = new HashMap<>();

        public boolean isKeywordEnd(){
            return isKeywordEnd;
        }

        public void setKeywordEnd(boolean keywordEnd){
            isKeywordEnd = keywordEnd;
        }

        //添加一个子节点
        public void addSubNode(Character c, TrieNode node){
            subNodes.put(c,node);
        }

        // 获取子节点
        public TrieNode getSubNode(Character c) {
            return subNodes.get(c);
        }
    }
}

